﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Controls;
using System.Windows.Forms;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using OpenCvSharp;
using OpenCvSharp.Dnn;
using OpenCvSharp.Extensions;

namespace JX
{
    public struct DetParam
    {
        public int modelWidth;
        public int modelHeight;
        public float confidenceThreshold;
        public bool outResultBmp;
    }

    public struct DetResult
    {
        public int index;
        public string label;
        public float confidence;
        public OpenCvSharp.Rect box;
    }

    public static class OnnxDetector
    {
        private static InferenceSession _session;
        private static List<string> _classNames = new List<string>();
        private const string ModelPath = "best.onnx";
        private const string ClassesPath = "classes.txt";
        private static readonly int InputWidth = 640;
        private static readonly int InputHeight = 640;
        private static DetParam param = new DetParam
        {
            modelWidth = OnnxDetector.InputWidth,
            modelHeight = OnnxDetector.InputHeight,
            confidenceThreshold = 0.8f,
            outResultBmp = false
        };
        static OnnxDetector()
        {
            try
            {
                var options = new SessionOptions();
                // 可选：启用 GPU 加速（DirectML）
                // options.AppendExecutionProvider_DirectML();
                options.EnableMemoryPattern = true;
                options.EnableCpuMemArena = true;
                options.EnableProfiling=true;
                options.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL;
                options.GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL;
                options.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_INFO;
                _session = new InferenceSession(ModelPath, options);

                if (File.Exists(ClassesPath))
                {
                    _classNames.AddRange(File.ReadAllLines(ClassesPath));
                }

                Console.WriteLine("模型和类别加载成功");
            }
            catch (Exception ex)
            {
                string errorMsg = $"加载模型失败: {ex.Message}";
                Console.WriteLine(errorMsg);
                MessageBox.Show(errorMsg, "模型加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public static Mat LoadImage(string filepath)
        {
            return Cv2.ImRead(filepath);
        }

        public static bool IsModelLoaded => _session != null;

        unsafe public static List<DetResult> Detect(Mat inputMat, DetParam param)
        {
            if (_session == null || _classNames.Count == 0)
                throw new InvalidOperationException("模型或类别未加载");

            // 预处理：BlobFromImage
            OpenCvSharp.Mat blob = CvDnn.BlobFromImage(inputMat, 1.0 / 255.0,
                new OpenCvSharp.Size(param.modelWidth, param.modelHeight), new Scalar(), true, false);

            try
            {
                int batchSize = 1;
                int channels = 3;
                int height = param.modelHeight;
                int width = param.modelWidth;
                int dataLength = batchSize * channels * height * width;

                float[] data = new float[dataLength];
                Marshal.Copy((IntPtr)blob.DataPointer, data, 0, dataLength);

                DenseTensor<float> tensor = new DenseTensor<float>(data, new[] { batchSize, channels, height, width });

                List<NamedOnnxValue> inputs = new List<NamedOnnxValue>
                {
                    NamedOnnxValue.CreateFromTensor("images", tensor)
                };
                var blobStopwatch = System.Diagnostics.Stopwatch.StartNew();

                IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results = _session.Run(inputs);
                blobStopwatch.Stop();
                Console.WriteLine($"[耗时] BlobFromImage + 数据拷贝: {blobStopwatch.Elapsed.TotalMilliseconds:F2} ms");
                try
                {
                    if (results == null || results.Count <= 0)
                        return new List<DetResult>();

                    Tensor<float> outputTensor = results[0].AsTensor<float>();
                    return ParseOutput(outputTensor, param, inputMat.Width, inputMat.Height);
                }
                finally
                {
                    results.Dispose(); // 手动释放结果
                }
            }
            finally
            {
                blob.Dispose(); // 手动释放 blob
            }
        }

        private static unsafe List<DetResult> ParseOutput(Tensor<float> output, DetParam param, int originalWidth, int originalHeight)
        {
            var blobStopwatch = System.Diagnostics.Stopwatch.StartNew();

            var detResults = new List<DetResult>();
            var confidences = new List<float>();
            var boxes = new List<OpenCvSharp.Rect>();
            var indices = new List<int>();

            int numDetections = output.Dimensions[1];
            int numClasses = output.Dimensions[2] - 5;

            float ratioW = (float)originalWidth / param.modelWidth;
            float ratioH = (float)originalHeight / param.modelHeight;

            // ✅ 正确获取浮点数数组
            float[] values = output.ToArray();

            fixed (float* p = values)
            {
                float* ptr = p;

                for (int i = 0; i < numDetections; i++)
                {
                    float objScore = *(ptr + 4);
                    if (objScore < param.confidenceThreshold)
                    {
                        ptr += output.Dimensions[2]; // 跳过该框
                        continue;
                    }

                    int classId = -1;
                    float maxClsScore = 0;

                    for (int c = 0; c < numClasses; c++)
                    {
                        float clsScore = *(ptr + 5 + c);
                        if (clsScore > maxClsScore)
                        {
                            maxClsScore = clsScore;
                            classId = c;
                        }
                    }

                    float confidence = objScore * maxClsScore;
                    if (confidence < param.confidenceThreshold)
                    {
                        ptr += output.Dimensions[2];
                        continue;
                    }

                    float cx = *ptr;
                    float cy = *(ptr + 1);
                    float w = *(ptr + 2);
                    float h = *(ptr + 3);

                    int left = (int)((cx - w / 2) * ratioW);
                    int top = (int)((cy - h / 2) * ratioH);
                    int right = (int)((cx + w / 2) * ratioW);
                    int bottom = (int)((cy + h / 2) * ratioH);

                    OpenCvSharp.Rect rect = new OpenCvSharp.Rect(left, top, right - left, bottom - top);
                    boxes.Add(rect);
                    confidences.Add(confidence);
                    indices.Add(classId);

                    ptr += output.Dimensions[2];
                }
            }

            // NMS 处理
            CvDnn.NMSBoxes(boxes, confidences, param.confidenceThreshold, 0.45f, out int[] rawIndices);
            List<int> nmsIndices = new List<int>(rawIndices);

            foreach (var idx in nmsIndices)
            {
                detResults.Add(new DetResult
                {
                    index = indices[idx],
                    label = _classNames[indices[idx]],
                    confidence = confidences[idx],
                    box = boxes[idx]
                });
            }
            blobStopwatch.Stop();
            Console.WriteLine($"[耗时] BlobFromImage + 解析结果: {blobStopwatch.Elapsed.TotalMilliseconds:F2} ms");
            return detResults;
        }

        public static void DrawResults(Mat image, List<DetResult> results)
        {
            Scalar color = new Scalar(0, 0, 255); // 红色
            foreach (var result in results)
            {
                Cv2.Rectangle(image, result.box, color, 2);

                string text = $"{result.label} {result.confidence:F2}";
                int baseline = 0;
                var textSize = Cv2.GetTextSize(text, HersheyFonts.HersheySimplex, 0.7, 2, out baseline);
                OpenCvSharp.Point topLeft = new OpenCvSharp.Point(result.box.Left, result.box.Top - 10);
                OpenCvSharp.Point bottomRight = new OpenCvSharp.Point(result.box.Left + textSize.Width, result.box.Top - textSize.Height - 10);
                var roi = new OpenCvSharp.Rect(
    topLeft.X,
    topLeft.Y,
    bottomRight.X - topLeft.X,
    bottomRight.Y - topLeft.Y);

                Cv2.Rectangle(image, roi, color, -1);
                Cv2.PutText(image, text, topLeft, HersheyFonts.HersheySimplex, 0.7, Scalar.White, 2);
            }
        }
        /// <summary>
        /// 将检测到的条形码框调整为水平方向，并返回裁剪后的图像。
        /// </summary>
        /// <param name="image">原始图像</param>
        /// <param name="result">检测结果</param>
        /// <returns>调整为水平方向的条形码图像</returns>
        public static Mat AdjustBarcodeOrientation(Mat image, DetResult result)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image), "图像不能为空");

            Rect box = result.box;

            // 1. 构建目标框的四个顶点
            OpenCvSharp.Point[] points = new[]
            {
        new OpenCvSharp.Point(box.Left, box.Top),
        new OpenCvSharp.Point(box.Right, box.Top),
        new OpenCvSharp.Point(box.Right, box.Bottom),
        new OpenCvSharp.Point(box.Left, box.Bottom)
    };

            // 2. 计算最小外接旋转矩形
            RotatedRect rotatedRect = Cv2.MinAreaRect(points);

            double angle = rotatedRect.Angle;

            // 3. 修正角度（如果矩形是竖直方向）
            if (rotatedRect.Size.Width < rotatedRect.Size.Height)
            {
                angle += 90; // 竖直方向，加90°使其变为水平
            }

            Console.WriteLine($"修正前角度: {rotatedRect.Angle}, 修正后角度: {angle}");

            // 4. 创建旋转矩阵（以旋转矩形中心为中心）
            Mat rotationMatrix = Cv2.GetRotationMatrix2D(rotatedRect.Center, angle, 1.0);

            // 5. 扩展图像防止旋转裁剪
            int pad = 100;
            Mat paddedImage=new Mat();
            Cv2.CopyMakeBorder(image, paddedImage, pad, pad, pad, pad, BorderTypes.Constant);

            // 更新旋转中心（考虑 padding）
            Point2f newCenter = new Point2f(rotatedRect.Center.X + pad, rotatedRect.Center.Y + pad);
            rotationMatrix.Set(0, 2, rotationMatrix.Get<double>(0, 2) + pad);
            rotationMatrix.Set(1, 2, rotationMatrix.Get<double>(1, 2) + pad);

            // 6. 旋转图像
            Mat rotatedImage = new Mat();
            Cv2.WarpAffine(paddedImage, rotatedImage, rotationMatrix, paddedImage.Size(), InterpolationFlags.Linear, BorderTypes.Constant, Scalar.Black);

            // 7. 获取旋转后的矩形顶点
            Point2f[] rotatedVertices = rotatedRect.Points();
            OpenCvSharp.Point[] transformedPoints = new OpenCvSharp.Point[4];
            for (int i = 0; i < 4; i++)
            {
                double x = rotationMatrix.Get<double>(0, 0) * rotatedVertices[i].X +
                           rotationMatrix.Get<double>(0, 1) * rotatedVertices[i].Y +
                           rotationMatrix.Get<double>(0, 2);
                double y = rotationMatrix.Get<double>(1, 0) * rotatedVertices[i].X +
                           rotationMatrix.Get<double>(1, 1) * rotatedVertices[i].Y +
                           rotationMatrix.Get<double>(1, 2);
                transformedPoints[i] = new OpenCvSharp.Point((int)x, (int)y);
            }

            // 8. 找出裁剪区域边界
            int minX = transformedPoints.Min(p => p.X);
            int minY = transformedPoints.Min(p => p.Y);
            int maxX = transformedPoints.Max(p => p.X);
            int maxY = transformedPoints.Max(p => p.Y);

            minX = Math.Max(0, minX);
            minY = Math.Max(0, minY);
            maxX = Math.Min(rotatedImage.Cols - 1, maxX);
            maxY = Math.Min(rotatedImage.Rows - 1, maxY);

            int width = maxX - minX;
            int height = maxY - minY;

            if (width <= 0 || height <= 0)
                throw new InvalidOperationException("裁剪区域无效");

            Rect adjustedBox = new Rect(minX, minY, width, height);

            // 9. 裁剪图像并克隆，避免后续释放问题
            using (Mat crop = new Mat(rotatedImage, adjustedBox))
            {
                return crop.Clone();
            }
        }

        /// <summary>
        /// 根据旋转后的图像大小调整目标框的坐标。
        /// </summary>
        /// <param name="rotatedRect">旋转矩形框。</param>
        /// <param name="imageSize">旋转后图像的大小。</param>
        /// <returns>调整后的目标框。</returns>
        private static Rect GetAdjustedBox(RotatedRect rotatedRect, OpenCvSharp.Size imageSize)
        {
            // 获取旋转矩形的顶点（Point2f 类型）
            Point2f[] points2F = rotatedRect.Points();

            // 手动转换为 Point 类型（整型坐标）
            OpenCvSharp.Point[] points = points2F.Select(p => new OpenCvSharp.Point((int)p.X, (int)p.Y)).ToArray();

            // 找到旋转后矩形的左上角和右下角
            int minX = int.MaxValue;
            int minY = int.MaxValue;
            int maxX = int.MinValue;
            int maxY = int.MinValue;

            foreach (var point in points)
            {
                minX = Math.Min(minX, point.X);
                minY = Math.Min(minY, point.Y);
                maxX = Math.Max(maxX, point.X);
                maxY = Math.Max(maxY, point.Y);
            }

            // 确保坐标在图像范围内
            minX = Math.Max(0, minX);
            minY = Math.Max(0, minY);
            maxX = Math.Min(imageSize.Width, maxX);
            maxY = Math.Min(imageSize.Height, maxY);

            return new Rect(minX, minY, maxX - minX, maxY - minY);
        }
        public static List<Mat> GetDetectionCrops(Mat image, List<DetResult> results)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image), "图像不能为空");

            if (results == null)
                throw new ArgumentNullException(nameof(results), "检测结果列表不能为空");

            List<Mat> crops = new List<Mat>();

            foreach (var result in results)
            {
                // 检查 Rect 是否合法
                if (result.box.Width <= 0 || result.box.Height <= 0 ||
                    result.box.X < 0 || result.box.Y < 0 ||
                    result.box.Right > image.Cols || result.box.Bottom > image.Rows)
                {
                    Console.WriteLine($"跳过非法区域: {result.box}");
                    continue;
                }

                // 裁剪图像区域
                using (Mat crop = new Mat(image, result.box))
                {
                    crops.Add(crop.Clone()); // 使用 Clone() 确保独立内存
                }
            }

            return crops;
        }
        private static CancellationTokenSource _cancellationTokenSource;

        public static async Task StartCameraAsync(PictureBox pictureBox,PictureBox viewbox, int cameraIndex = 0)
        {
            if (pictureBox == null)
                throw new ArgumentNullException(nameof(pictureBox));

            // 如果已经有摄像头在运行，则先停止它
            StopCamera();

            // 创建新的 CancellationTokenSource
            _cancellationTokenSource = new CancellationTokenSource();

            await Task.Run(async () =>
            {
                using (var capture = new VideoCapture(cameraIndex))
                {
                    if (!capture.IsOpened())
                    {
                        MessageBox.Show("无法打开摄像头！");
                        return;
                    }
                    capture.Set(VideoCaptureProperties.FrameWidth, 1280);  // 设置分辨率宽度
                    capture.Set(VideoCaptureProperties.FrameHeight, 720);   // 设置分辨率高度
                    capture.Set(VideoCaptureProperties.Fps, 60);
                    Mat frame = new Mat();
                    while (!_cancellationTokenSource.Token.IsCancellationRequested)
                    {
                        capture.Read(frame);
                        if (frame.Empty()) continue;
                        var now = DateTime.Now;
                        Mat resizedMat = new Mat();

                        // 使用 INTER_AREA 插值方法进行缩小（适合图像缩小）
                        Cv2.Resize(frame, resizedMat, new OpenCvSharp.Size(640, 640), interpolation: InterpolationFlags.Area);
                        Bitmap videobit = resizedMat.ToBitmap();
                        if (!((now - _lastRunTime) < _minInterval))
                        {
                            _= RunVideo(viewbox, resizedMat, frame);
                            _lastRunTime = now;
                        }
                        // 创建目标 Mat

                        pictureBox.Invoke((MethodInvoker)delegate
                        {
                            if (pictureBox.IsDisposed) return;
                            pictureBox.Image?.Dispose();
                            pictureBox.Image = (System.Drawing.Image)videobit.Clone(); // 避免资源被释放
                        });
                    }
                }
            });
        }
        private static DateTime _lastRunTime = DateTime.MinValue;
        private static readonly TimeSpan _minInterval = TimeSpan.FromMilliseconds(30);

        public static async Task RunVideo(PictureBox showbox, Mat resizedFrame, Mat originalFrame)
        {
            // 控制执行频率：最多每 30ms 执行一次
            var now = DateTime.Now;
            if ((now - _lastRunTime) < _minInterval)
                return;

            // 检测并绘制结果（使用缩放图像）
            List<DetResult> results = OnnxDetector.Detect(resizedFrame, param);
            if (results.Count == 0)
            {
                _lastRunTime = now;
                return;
            }
            float ratioX = (float)originalFrame.Width / resizedFrame.Width;
            float ratioY = (float)originalFrame.Height / resizedFrame.Height;

            for (int i = 0; i < results.Count; i++)
            {
                DetResult result = results[i];

                Rect scaledBox = result.box;

                int x = (int)(scaledBox.X * ratioX);
                int y = (int)(scaledBox.Y * ratioY);
                int width = (int)(scaledBox.Width * ratioX);
                int height = (int)(scaledBox.Height * ratioY);

                // 更新框到原始图坐标
                results[i] = new DetResult
                {
                    index = result.index,
                    label = result.label,
                    confidence = result.confidence,
                    box = new Rect(x, y, width, height)
                };
            }
            // 获取裁剪图像（使用原始图像）
            List<Mat> crops = OnnxDetector.GetDetectionCrops(originalFrame, results);

            // 如果有裁剪结果，显示第一个目标图像
            if (crops.Count > 0)
            {
                // 释放所有裁剪图像
                foreach(var crop in crops)
                {
                    showbox.Invoke((MethodInvoker)delegate
                    {
                        if (showbox.IsDisposed) return;
                        showbox.Image?.Dispose();
                        var cropbit = crop.ToBitmap();
                        showbox.Image = (System.Drawing.Image)cropbit.Clone(); // 避免资源被释放
                    });
                    crop.Dispose();
                }
            }

            _lastRunTime = now;
        }
        public static void StopCamera()
        {
            if (_cancellationTokenSource != null)
            {
                _cancellationTokenSource.Cancel();
                _cancellationTokenSource.Dispose();
                _cancellationTokenSource = null;
            }
        }
    }
}